﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Properties;

namespace Borg
{
    public class SlotInfo : RepositoryElement
    {
        internal static int nextId = 1;

        internal readonly SlotTypeInfo slotTypeInfo;
        internal readonly SlotPropertyInfo propertyInfo;
        internal readonly SlotEventHandlerInfo eventHandlerInfo;
        internal readonly ExtensionInfo extensionInfo;
        internal RepositoryCollection<PlugTypeInfo> registeredPlugTypeInfos;
        internal RepositoryCollection<PlugInfo> pluggedPlugInfos;
        internal readonly RepositoryCollection<PlugInfo> selectedPlugs;

        internal bool isOpen;
        internal bool lazyLoad;
        internal bool autoRegister;
        internal bool autoPlug;
        internal bool autoOpen;
        internal bool autoRelease;
        internal bool unique;

        internal SelectionMode selectionMode;

        public event CancelRegisterEventHandler Registering;
        public event RegisterEventHandler Registered;

        public event CancelRegisterEventHandler Deregistering;
        public event RegisterEventHandler Deregistered;

        public event CancelPlugEventHandler Plugging;
        public event CancelPlugEventHandler PluggingCanceled;
        public event PlugEventHandler Plugged;

        public event CancelPlugEventHandler Unplugging;
        public event PlugEventHandler Unplugged;

        public event CancelSlotEventHandler Opening;
        public event SlotEventHandler Opened;

        public event CancelSlotEventHandler Closing;
        public event SlotEventHandler Closed;

        public event SelectionChangedEventHandler SelectionChanged;

        public override string Name
        {
            get { return slotTypeInfo.Name; }
        }

        public override Assembly Assembly
        {
            get { return extensionInfo.extensionTypeInfo.pluginInfo.assembly; }
        }

        public SlotTypeInfo SlotTypeInfo
        {
            get { return slotTypeInfo; }
        }

        public ExtensionInfo ExtensionInfo
        {
            get { return extensionInfo; }
        }

        public bool IsOpen
        {
            get { return isOpen; }
        }

        public SlotDefinition SlotDefinition
        {
            get { return slotTypeInfo.SlotDefinition; }
        }

        public RepositoryCollection<PlugTypeInfo> RegisteredPlugTypeInfos
        {
            get { return registeredPlugTypeInfos; }
        }

        public RepositoryCollection<PlugInfo> PluggedPlugInfos
        {
            get { return pluggedPlugInfos; }
        }

        public bool AutoOpen
        {
            get { return autoOpen; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoOpen(this, value);
            }
        }

        public bool AutoRegister
        {
            get { return autoRegister; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoRegister(this, value);
            }
        }

        public bool AutoPlug
        {
            get { return autoPlug; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoPlug(this, value);
            }
        }

        public bool LazyLoad
        {
            get { return lazyLoad; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetLazyLoad(this, value);
            }
        }

        public bool Unique
        {
            get { return unique; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetUnique(this, value);
            }
        }

        public bool AutoRelease
        {
            get { return autoRelease; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoRelease(this, value);
            }
        }

        public SelectionMode SelectionMode
        {
            get { return selectionMode; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetSelectionMode(this, value);
            }
        }

        public IList<PlugInfo> SelectedPlugs
        {
            get { return selectedPlugs; }
        }

        public PlugInfo SelectedPlug
        {
            get { return selectedPlugs.Count > 0 ? selectedPlugs[0] : null; }
            set { SetSelectedPlugs(new[] { value }); }
        }

        public int SelectedPlugIndex
        {
            get { return selectedPlugs.Count == 0 ? -1 : pluggedPlugInfos.IndexOf(selectedPlugs[0]); }
            set
            {
                if (value == -1)
                {
                    if (selectedPlugs.Count == 0)
                        return;

                    SetSelectedPlugs(null);
                }
                else
                    SetSelectedPlugs(new[] { pluggedPlugInfos[value] });
            }
        }

        static SlotInfo()
        { }

        internal SlotInfo(SlotTypeInfo slotTypeInfo, ExtensionInfo extensionInfo)
        {
            id = nextId++;

            this.slotTypeInfo = slotTypeInfo;
            this.extensionInfo = extensionInfo;

            lazyLoad = slotTypeInfo.LazyLoad;
            autoRegister = slotTypeInfo.AutoRegister;
            autoPlug = slotTypeInfo.AutoPlug;
            autoOpen = slotTypeInfo.AutoOpen;
            autoRelease = slotTypeInfo.AutoRelease;
            unique = slotTypeInfo.Unique;

            selectionMode = slotTypeInfo.SelectionMode;

            propertyInfo = new SlotPropertyInfo(this);
            eventHandlerInfo = new SlotEventHandlerInfo(this);
            registeredPlugTypeInfos = new RepositoryCollection<PlugTypeInfo>(new List<PlugTypeInfo>());
            pluggedPlugInfos = new RepositoryCollection<PlugInfo>(new List<PlugInfo>());
            selectedPlugs = new RepositoryCollection<PlugInfo>(new List<PlugInfo>());

            if (!slotTypeInfo.eventHandlerMethodInfosSet)
                return;

            if (slotTypeInfo.MiOnOpening != null)
                Opening += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnOpening).InvokeMethod;

            if (slotTypeInfo.MiOnOpened != null)
                Opened += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnOpened).InvokeMethod;

            if (slotTypeInfo.MiOnClosing != null)
                Closing += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnClosing).InvokeMethod;

            if (slotTypeInfo.MiOnClosed != null)
                Closed += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnClosed).InvokeMethod;

            if (slotTypeInfo.MiOnRegistering != null)
                Registering += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnRegistering).InvokeMethod;

            if (slotTypeInfo.MiOnRegistered != null)
                Registered += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnRegistered).InvokeMethod;

            if (slotTypeInfo.MiOnDeregistering != null)
                Deregistering += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnDeregistering).InvokeMethod;

            if (slotTypeInfo.MiOnDeregistered != null)
                Deregistered += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnDeregistered).InvokeMethod;

            if (slotTypeInfo.MiOnPlugging != null)
                Plugging += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnPlugging).InvokeMethod;

            if (slotTypeInfo.MiOnPluggingCanceled != null)
                PluggingCanceled += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnPluggingCanceled).InvokeMethod;

            if (slotTypeInfo.MiOnPlugged != null)
                Plugged += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnPlugged).InvokeMethod;

            if (slotTypeInfo.MiOnUnplugging != null)
                Unplugging += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnUnplugging).InvokeMethod;

            if (slotTypeInfo.MiOnUnplugged != null)
                Unplugged += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnUnplugged).InvokeMethod;

            if (slotTypeInfo.MiOnSelectionChanged != null)
                SelectionChanged += new ReflectionEventHandler(
                    extensionInfo.Object, slotTypeInfo.MiOnSelectionChanged).InvokeMethod;
        }

        public ExtensionTypeInfo GetRegisteredExtensionType()
        {
            return registeredPlugTypeInfos.Count != 0
                ? registeredPlugTypeInfos[0].extensionTypeInfo
                : null;
        }

        public IEnumerable<ExtensionTypeInfo> GetRegisteredExtensionTypes()
        {
            return registeredPlugTypeInfos.Select(plugTypeInfo => plugTypeInfo.extensionTypeInfo);
        }

        public T GetPluggedExtension<T>()
        {
            return pluggedPlugInfos.Count == 0
                       ? default(T)
                       : (T)pluggedPlugInfos[pluggedPlugInfos.Count - 1].extensionInfo.Object;
        }

        public IEnumerable<T> GetPluggedExtensions<T>()
        {
            return pluggedPlugInfos.Select(plugInfo => (T)plugInfo.extensionInfo.Object);
        }

        public T GetSelectedExtension<T>()
        {
            return selectedPlugs.Count == 0
                       ? default(T)
                       : (T)selectedPlugs[selectedPlugs.Count - 1].extensionInfo.Object;
        }

        public IEnumerable<T> GetSelectedExtensions<T>()
        {
            return selectedPlugs.Select(plugInfo => (T)plugInfo.extensionInfo.Object);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Open()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Open(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Close()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Close(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Register()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Register(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Register(PlugTypeInfo pti)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Register(this, pti);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Deregister()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Deregister(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Deregister(PlugTypeInfo pti)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Deregister(this, pti);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Plug()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Plug(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Plug(PlugInfo pi)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Plug(this, pi);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Plug(PlugTypeInfo pti)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Plug(this, pti);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Unplug()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Unplug(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Unplug(PlugInfo pi)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Unplug(this, pi);
        }

        public bool SetSelectedPlugs(params PlugInfo[] plugs)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            return Runtime.Composer.SetSelectedPlugs(this, plugs);
        }

        public void SelectAllPlugs()
        {
            SelectPlugs(pluggedPlugInfos.ToArray());
        }

        public bool SelectPlugs(params PlugInfo[] plugs)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            return Runtime.Composer.SelectPlugs(this, plugs);
        }

        public bool DeselectPlugs(params PlugInfo[] plugs)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            return Runtime.Composer.DeselectPlugs(this, plugs);
        }

        internal bool InternalOpen(bool isManual, uint taskGroupId, Assembly initiator, bool enqueueRegisterTask)
        {
            if (isManual)
            {
                if (isOpen)
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnOpeningSlot,
                            new object[]
                                {
                                    id.ToString(CultureInfo.InvariantCulture),
                                    Name,
                                    extensionInfo.Name,
                                    string.Format("\n  {0}", Messages.SlotAlreadyOpen)
                                }));
                    return false;
                }
            }
            else if (isOpen || (!autoOpen || !Runtime.AutoOpen))
                return false;

            if (!slotTypeInfo.IsQualified && slotTypeInfo.IsQualificationMissing)
            {
                slotTypeInfo.ForceQualify();

                if (!slotTypeInfo.IsQualified)
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnOpeningSlot,
                            new object[]
                                {
                                    id.ToString(CultureInfo.InvariantCulture),
                                    Name,
                                    extensionInfo.Name,
                                    string.Format("\n  {0}", Messages.SlotTypeIsNotQualified)
                                }));
                    return false;
                }
            }

            if (!slotTypeInfo.eventHandlerMethodInfosSet)
                slotTypeInfo.VerifyEventHandlerMethods(false);

            var args = new CancelSlotEventArgs(this, initiator);
            OnOpening(this, args);

            if (args.Cancel)
            {
                Runtime.OnLogEvent(
                    this, new LogEventArgs(
                              MessageType.Info,
                              VerbosityLevel.Normal,
                              Messages.OpeningCanceled,
                              new object[]
                                  {
                                      id.ToString(CultureInfo.InvariantCulture),
                                      Name,
                                      extensionInfo.Name
                                  }));
                return false;
            }

            isOpen = true;
            OnOpened(this, new SlotEventArgs(this, initiator));

            if (enqueueRegisterTask)
                Runtime.TaskQueue.Enqueue(
                    TaskFactory.CreateRegister(
                        this,
                        false,
                        taskGroupId,
                        Runtime.Assembly));

            return true;
        }

        internal void InternalClose(Assembly initiator, bool force)
        {
            if (!isOpen)
                return;

            var args = new CancelSlotEventArgs(this, initiator);
            OnClosing(this, args);

            if (!force && args.Cancel)
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.ClosingCanceled,
                        new object[]
                            {
                                id.ToString(CultureInfo.InvariantCulture),
                                Name,
                                extensionInfo.Name
                            }));
            else
            {
                InternalDeregister(Runtime.assembly, true);
                isOpen = false;
                OnClosed(this, new SlotEventArgs(this, initiator));
            }
        }

        internal void InternalRegister(bool isManual, uint taskGroupId, Assembly initiator)
        {
            foreach (var pti in Runtime.Repository.ExtensionTypeInfos.
                Select(eti => eti.plugTypeInfos[Name]).
                Where(pti => pti != null && !registeredPlugTypeInfos.Contains(pti)))
                InternalRegister(pti, isManual, taskGroupId, initiator, true);
        }

        internal bool InternalRegister(PlugTypeInfo pti, bool isManual, uint taskGroupId, Assembly initiator, bool enqueuePlugTask)
        {
            var eti = pti.ExtensionTypeInfo;

            if (isManual)
            {
                if (Name != pti.Name)
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnRegisteringExtension,
                            new object[]
                                {
                                    eti.id.ToString(CultureInfo.InvariantCulture),
                                    eti.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.PlugIsNotCompatibleToSlot)
                                }));
                    return false;
                }

                if (!isOpen)
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnRegisteringExtension,
                            new object[]
                                {
                                    eti.id.ToString(CultureInfo.InvariantCulture),
                                    eti.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.SlotIsClosed)
                                }));
                    return false;
                }

                if (registeredPlugTypeInfos.Contains(pti))
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnRegisteringExtension,
                            new object[]
                                {
                                    eti.id.ToString(CultureInfo.InvariantCulture),
                                    eti.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.PlugAlreadyRegistered)
                                }));
                    return false;
                }

                if (!slotTypeInfo.Multiple && registeredPlugTypeInfos.Count > 0)
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnRegisteringExtension,
                            new object[]
                                {
                                    eti.id.ToString(CultureInfo.InvariantCulture),
                                    eti.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.SlotDoesNotAllowMultiple)
                                }));
                    return false;
                }

                if (IsCircularRegistering(pti))
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Detailed,
                            Messages.RegisteringWarning,
                            new object[]
                                {
                                    eti.id.ToString(CultureInfo.InvariantCulture),
                                    eti.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.ManualCircularRegistering)
                                }));
            }
            else
            {
                if (!autoRegister ||
                    !Runtime.AutoRegister ||
                    (!pti.AutoRegister || !isOpen) ||
                    registeredPlugTypeInfos.Contains(pti) ||
                    !slotTypeInfo.Multiple &&
                    registeredPlugTypeInfos.Count > 0)
                    return false;

                if (IsCircularRegistering(pti))
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Info,
                            VerbosityLevel.Diagnostic,
                            Messages.RegisteringAborted,
                            new object[]
                                {
                                    eti.id.ToString(CultureInfo.InvariantCulture),
                                    eti.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.AutoCircularRegistering)
                                }));
                    return false;
                }
            }

            var pluginInfo = eti.PluginInfo;

            if (!pluginInfo.IsQualified)
            {
                if (pluginInfo.IsQualificationMissing)
                {
                    pluginInfo.ForceQualify();

                    if (!pluginInfo.IsQualified)
                    {
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.ErrorOnRegisteringPlugin,
                                new object[]
                                    {
                                        pluginInfo.id.ToString(CultureInfo.InvariantCulture),
                                        pluginInfo.Name,
                                        string.Format("\n  {0}", Messages.PluginIsNotQualified)
                                    }));
                        return false;
                    }
                }
                else
                {
                    if (isManual)
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.ErrorOnRegisteringPlugin,
                                new object[]
                                    {
                                        pluginInfo.id.ToString(CultureInfo.InvariantCulture),
                                        pluginInfo.Name,
                                        string.Format("\n  {0}", Messages.PluginIsNotQualified)
                                    }));
                    return false;
                }
            }

            if (!eti.IsQualified)
            {
                if (eti.IsQualificationMissing)
                {
                    eti.InternalMainQualify();

                    if (!eti.IsQualified)
                    {
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.ErrorOnRegisteringExtension,
                                new object[]
                                    {
                                        eti.id.ToString(CultureInfo.InvariantCulture),
                                        eti.Name,
                                        extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                        extensionInfo.Name,
                                        Name,
                                        string.Format("\n  {0}", Messages.PluginIsNotQualified)
                                    }));
                        return false;
                    }
                }
                else
                {
                    if (isManual)
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.ErrorOnRegisteringExtension,
                                new object[]
                                    {
                                        eti.id.ToString(CultureInfo.InvariantCulture),
                                        eti.Name,
                                        extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                        extensionInfo.Name,
                                        Name,
                                        string.Format("\n  {0}", Messages.PluginIsNotQualified)
                                    }));
                    return false;
                }
            }

            if (!pti.IsQualified)
            {
                if (pti.IsQualificationMissing)
                {
                    pti.ForceQualify();

                    if (!pti.IsQualified)
                    {
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.ErrorOnRegisteringExtension,
                                new object[]
                                    {
                                        eti.id.ToString(CultureInfo.InvariantCulture),
                                        eti.Name,
                                        extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                        extensionInfo.Name,
                                        Name,
                                        string.Format("\n  {0}", Messages.PlugTypeIsNotQualified)
                                    }));
                        return false;
                    }
                }
                else
                {
                    if (isManual)
                        Runtime.OnLogEvent(
                            this,
                            new LogEventArgs(
                                MessageType.Warning,
                                VerbosityLevel.Normal,
                                Messages.ErrorOnRegisteringExtension,
                                new object[]
                                    {
                                        eti.id.ToString(CultureInfo.InvariantCulture),
                                        eti.Name,
                                        extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                        extensionInfo.Name,
                                        Name,
                                        string.Format("\n  {0}", Messages.PlugTypeIsNotQualified)
                                    }));
                    return false;
                }
            }

            var args = new CancelRegisterEventArgs(this, pti, initiator);
            OnRegistering(this, args);

            if (args.Cancel)
            {
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Detailed,
                        Messages.RegisteringCanceled,
                        new object[]
                            {
                                pti.extensionTypeInfo.id.ToString(CultureInfo.InvariantCulture),
                                pti.extensionTypeInfo.Name,
                                extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                extensionInfo.Name,
                                slotTypeInfo.Name
                            }));
                return false;
            }

            registeredPlugTypeInfos.Add(pti);
            pti.registeredInSlots.Add(this);

            OnRegistered(this, new RegisterEventArgs(this, pti, initiator));

            if (enqueuePlugTask)
                Runtime.TaskQueue.Enqueue(
                    TaskFactory.CreatePlug(
                        this,
                        pti,
                        false,
                        taskGroupId,
                        Runtime.assembly));

            return true;
        }

        internal void InternalDeregister(Assembly initiator, bool force)
        {
            for (var i = registeredPlugTypeInfos.Count - 1; i >= 0; --i)
                InternalDeregister(registeredPlugTypeInfos[i], initiator, force);
        }

        internal void InternalDeregister(PlugTypeInfo pti, Assembly initiator, bool force)
        {
            if (pti == null)
                return;

            var args = new CancelRegisterEventArgs(this, pti, initiator);
            OnDeregistering(this, args);

            if (!force && args.Cancel)
            {
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.DeregisteringCanceled,
                        new object[]
                            {
                                id.ToString(CultureInfo.InvariantCulture),
                                Name,
                                extensionInfo.Name
                            }));
            }
            else
            {
                if (!registeredPlugTypeInfos.Contains(pti))
                    return;

                for (var i = pluggedPlugInfos.Count - 1; i >= 0; --i)
                    if (pluggedPlugInfos[i].plugTypeInfo == pti)
                        InternalUnplug(pluggedPlugInfos[i], Runtime.assembly, true);

                registeredPlugTypeInfos.Remove(pti);
                pti.registeredInSlots.Remove(this);
                OnDeregistered(this, new RegisterEventArgs(this, pti, initiator));
            }
        }

        internal void InternalPlug(uint taskGroupId, Assembly initiator)
        {
            foreach (var pti in registeredPlugTypeInfos)
                InternalPlug(pti, true, taskGroupId, initiator);
        }

        internal bool InternalPlug(PlugTypeInfo pti, bool isManual, uint taskGroupId, Assembly initiator)
        {
            if (!isManual && (!autoPlug || !Runtime.AutoPlug) || !registeredPlugTypeInfos.Contains(pti))
                return false;

            ExtensionInfo extension;

            if (!unique)
            {
                if (pti.extensionTypeInfo.sharedExtension != null)
                {
                    if (pluggedPlugInfos.Contains(pti.extensionTypeInfo.sharedExtension.plugInfos[Name]))
                        return false;

                    extension = pti.extensionTypeInfo.sharedExtension;
                }
                else
                {
                    if (!isManual && (Runtime.LazyLoad || lazyLoad || !pti.AutoPlug))
                        return false;

                    extension =
                        pti.extensionTypeInfo.InternalCreateSharedExtension(
                            null,
                            AppDomain.CurrentDomain,
                            isManual,
                            taskGroupId,
                            Runtime.Assembly);
                }
            }
            else
            {
                if (pluggedPlugInfos.Any(plugInfo => plugInfo.plugTypeInfo == pti))
                    return false;

                if (!isManual && (Runtime.LazyLoad || lazyLoad || !pti.AutoPlug))
                    return false;

                extension =
                    pti.extensionTypeInfo.InternalCreateExtension(
                        false,
                        false,
                        AppDomain.CurrentDomain,
                        Runtime.Assembly);
            }

            return extension != null &&
                InternalPlug(extension.plugInfos[Name], isManual, taskGroupId, initiator, true);
        }

        internal bool InternalPlug(PlugInfo pi, bool isManual, uint taskGroupId, Assembly initiator, bool enqueueOpenSlotsTask)
        {
            if (pi == null)
                return false;

            if (isManual)
            {
                if (!registeredPlugTypeInfos.Contains(pi.plugTypeInfo))
                {
                    Runtime.OnLogEvent(
                        this, new LogEventArgs(
                                  MessageType.Warning,
                                  VerbosityLevel.Normal,
                                  Messages.ErrorOnPluggingExtension,
                                  new object[]
                                      {
                                          pi.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                          pi.extensionInfo.Name,
                                          extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                          extensionInfo.Name,
                                          Name,
                                          string.Format("\n  {0}", Messages.PlugIsNotRegistered)
                                      }));
                    return false;
                }

                if (pluggedPlugInfos.Contains(pi))
                {
                    Runtime.OnLogEvent(
                        this,
                        new LogEventArgs(
                            MessageType.Warning,
                            VerbosityLevel.Normal,
                            Messages.ErrorOnPluggingExtension,
                            new object[]
                                {
                                    pi.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    pi.extensionInfo.Name,
                                    extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                    extensionInfo.Name,
                                    Name,
                                    string.Format("\n  {0}", Messages.PlugIsAlreadyPlugged)
                                }));
                    return false;
                }
            }
            else if (!autoPlug ||
                !pi.autoPlug ||
                (!Runtime.AutoPlug || !registeredPlugTypeInfos.Contains(pi.plugTypeInfo)) ||
                pluggedPlugInfos.Contains(pi) ||
                unique &&
                pi.extensionInfo == pi.extensionInfo.extensionTypeInfo.sharedExtension)
                return false;

            var args = new CancelPlugEventArgs(this, pi, initiator);
            OnPlugging(this, args);

            if (args.Cancel)
            {
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Detailed,
                        Messages.PluggingCanceled,
                        new object[]
                            {
                                pi.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                pi.extensionInfo.Name,
                                extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                extensionInfo.Name,
                                slotTypeInfo.Name
                            }));
                return false;
            }

            pluggedPlugInfos.Add(pi);
            pi.pluggedInSlots.Add(this);

            if (pi.extensionInfo.owner == null)
                pi.extensionInfo.owner = this;

            OnPlugged(this, new PlugEventArgs(this, pi, initiator));

            if (enqueueOpenSlotsTask)
                Runtime.TaskQueue.Enqueue(
                    TaskFactory.CreateOpenSlots(
                        pi.extensionInfo,
                        false,
                        taskGroupId,
                        Runtime.Assembly));

            return true;
        }

        internal void InternalUnplug(Assembly initiator, bool force)
        {
            for (var i = pluggedPlugInfos.Count - 1; i >= 0; --i)
                InternalUnplug(pluggedPlugInfos[i], initiator, force);
        }

        internal void InternalUnplug(PlugInfo pi, Assembly initiator, bool force)
        {
            var args = new CancelPlugEventArgs(this, pi, initiator);
            OnUnplugging(this, args);

            if (!force && args.Cancel)
            {
                Runtime.OnLogEvent(
                    this,
                    new LogEventArgs(
                        MessageType.Info,
                        VerbosityLevel.Normal,
                        Messages.UnpluggingCanceled,
                        new object[]
                            {
                                pi.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                pi.extensionInfo.Name,
                                extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                                extensionInfo.Name,
                                slotTypeInfo.Name
                            }));
            }
            else
            {
                if (!pluggedPlugInfos.Remove(pi))
                    return;

                pi.pluggedInSlots.Remove(this);
                InternalDeselectPlugs(new[] { pi });
                OnUnplugged(this, new PlugEventArgs(this, pi, initiator));

                if (!pi.extensionInfo.IsPlugged &&
                    pi.extensionInfo.autoRelease &&
                    autoRelease &&
                    Runtime.AutoRelease)
                    pi.extensionInfo.InternalRelease(Runtime.assembly, false);
            }
        }

        internal bool InternalSelectPlugs(params PlugInfo[] plugs)
        {
            if (plugs == null || plugs.Length == 0)
                return true;

            IList<PlugInfo> addedPlugs = new List<PlugInfo>();
            var status = true;

            if (selectionMode == SelectionMode.Single)
                return InternalSetSelectedPlugs(new[] { plugs[0] });

            foreach (var plugInfo in plugs.Where(plugInfo => !selectedPlugs.Contains(plugInfo)))
            {
                if (!pluggedPlugInfos.Contains(plugInfo))
                    status = false;
                else
                {
                    selectedPlugs.Add(plugInfo);
                    addedPlugs.Add(plugInfo);
                }
            }

            if (addedPlugs.Count > 0)
                OnSelectionChanged(new SelectionChangedEventArgs(
                    this, addedPlugs, new List<PlugInfo>()));

            return status;
        }

        internal bool InternalDeselectPlugs(params PlugInfo[] plugs)
        {
            IList<PlugInfo> removedPlugs = new List<PlugInfo>();

            foreach (var plugInfo in plugs.Where(plugInfo => selectedPlugs.Contains(plugInfo)))
            {
                selectedPlugs.Remove(plugInfo);
                removedPlugs.Add(plugInfo);
            }

            if (removedPlugs.Count > 0)
                OnSelectionChanged(new SelectionChangedEventArgs(
                    this, new List<PlugInfo>(), removedPlugs));

            return true;
        }

        internal bool InternalSetSelectedPlugs(params PlugInfo[] plugs)
        {
            IList<PlugInfo> addedPlugs = new List<PlugInfo>();
            IList<PlugInfo> removedPlugs = new List<PlugInfo>(selectedPlugs);

            selectedPlugs.Clear();
            var status = true;

            if (plugs != null)
            {
                if (plugs.Length > 1 && selectionMode == SelectionMode.Single)
                {
                    plugs = new[] { plugs[0] };
                    status = false;
                }

                foreach (var plugInfo in plugs)
                {
                    if (!pluggedPlugInfos.Contains(plugInfo))
                        status = false;
                    else
                    {
                        selectedPlugs.Add(plugInfo);

                        if (removedPlugs.Contains(plugInfo))
                            removedPlugs.Remove(plugInfo);
                        else
                            addedPlugs.Add(plugInfo);
                    }
                }
            }

            if (addedPlugs.Count > 0 || removedPlugs.Count > 0)
                OnSelectionChanged(new SelectionChangedEventArgs(this, addedPlugs, removedPlugs));

            return status;
        }

        internal bool HasExtensionPlugged(ExtensionTypeInfo extensionTypeInfo)
        {
            return pluggedPlugInfos.
                Any(plugInfo => extensionTypeInfo.plugTypeInfos.Contains(plugInfo.plugTypeInfo));
        }

        internal bool IsCircularRegistering(PlugTypeInfo contributer)
        {
            return IsCircularRegistering(
                contributer.extensionTypeInfo,
                extensionInfo.extensionTypeInfo,
                new List<ExtensionTypeInfo>());
        }

        internal static bool IsCircularRegistering(ExtensionTypeInfo start,
            ExtensionTypeInfo host, ICollection<ExtensionTypeInfo> visited)
        {
            if (host == start)
                return true;

            if (visited.Contains(host))
                return false;

            visited.Add(host);

            return
                host.plugTypeInfos.
                SelectMany(plugTypeInfo => plugTypeInfo.registeredInSlots).
                Any(slotInfo => IsCircularRegistering(
                    start, slotInfo.extensionInfo.extensionTypeInfo, visited));
        }

        internal void OnOpening(object sender, CancelSlotEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.SlotOpening,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name,
                            extensionInfo.Name
                        }));

            if (Opening != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Opening",
                        Name),
                    Opening,
                    this,
                    args);

            slotTypeInfo.OnOpening(this, args);
        }

        internal void OnOpened(object sender, SlotEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.SlotOpened,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name,
                            extensionInfo.Name
                        }));

            if (Opened != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Opened",
                        Name),
                    Opened,
                    this,
                    args);

            slotTypeInfo.OnOpened(this, args);
        }

        internal void OnClosing(object sender, CancelSlotEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.SlotClosing,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name,
                            extensionInfo.Name
                        }));

            if (Closing != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Closing",
                        Name),
                    Closing,
                    this,
                    args);

            slotTypeInfo.OnClosing(this, args);
        }

        internal void OnClosed(object sender, SlotEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.SlotClosed,
                    new object[]
                        {
                            id.ToString(CultureInfo.InvariantCulture),
                            Name,
                            extensionInfo.Name
                        }));

            if (Closed != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Closed",
                        Name),
                    Closed,
                    this,
                    args);

            slotTypeInfo.OnClosed(this, args);
        }

        internal void OnRegistering(object sender, CancelRegisterEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionTypeRegistering,
                    new object[]
                        {
                            args.plugTypeInfo.extensionTypeInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugTypeInfo.extensionTypeInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            Name
                        }));

            if (Registering != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Registering",
                        Name),
                    Registering,
                    this,
                    args);

            slotTypeInfo.OnRegistering(this, args);
        }

        internal void OnRegistered(object sender, RegisterEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionTypeRegistered,
                    new object[]
                        {
                            args.plugTypeInfo.extensionTypeInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugTypeInfo.extensionTypeInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            slotTypeInfo.Name
                        }));

            if (Registered != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Registered",
                        Name),
                    Registered,
                    this,
                    args);

            slotTypeInfo.OnRegistered(this, args);
        }

        internal void OnDeregistering(object sender, CancelRegisterEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionTypeDeregistering,
                    new object[]
                        {
                            args.plugTypeInfo.extensionTypeInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugTypeInfo.extensionTypeInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            Name
                        }));

            if (Deregistering != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Deregistering",
                        Name),
                    Deregistering,
                    this,
                    args);

            slotTypeInfo.OnDeregistering(this, args);
        }

        internal void OnDeregistered(object sender, RegisterEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionTypeDeregistered,
                    new object[]
                        {
                            args.plugTypeInfo.extensionTypeInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugTypeInfo.extensionTypeInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            slotTypeInfo.Name
                        }));

            if (Deregistered != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Deregistered",
                        Name),
                    Deregistered,
                    this,
                    args);

            slotTypeInfo.OnDeregistered(this, args);
        }

        internal void OnPlugging(object sender, CancelPlugEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionPlugging,
                    new object[]
                        {
                            args.plugInfo.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugInfo.extensionInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            slotTypeInfo.Name
                        }));

            args.plugInfo.OnPlugging(sender, args);

            if (Plugging != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Plugging",
                        Name),
                    Plugging,
                    this,
                    args);

            slotTypeInfo.OnPlugging(this, args);

            if (!args.Cancel)
                return;

            OnPluggingCanceled(sender, args);
        }

        internal void OnPluggingCanceled(object sender, CancelPlugEventArgs args)
        {
            if (PluggingCanceled != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "PluggingCanceled",
                        Name),
                    PluggingCanceled,
                    this,
                    args);

            slotTypeInfo.OnPluggingCanceled(this, args);
        }

        internal void OnPlugged(object sender, PlugEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionPlugged,
                    new object[]
                        {
                            args.plugInfo.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugInfo.extensionInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            slotTypeInfo.Name
                        }));

            args.plugInfo.OnPlugged(sender, args);

            if (Plugged != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Plugged",
                        Name),
                    Plugged,
                    this,
                    args);

            slotTypeInfo.OnPlugged(this, args);
        }

        internal void OnUnplugging(object sender, CancelPlugEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionUnplugging,
                    new object[]
                        {
                            args.plugInfo.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugInfo.extensionInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            Name
                        }));

            args.plugInfo.OnUnplugging(sender, args);

            if (Unplugging != null)
                Runtime.InvokeCancelEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Unplugging",
                        Name),
                    Unplugging,
                    this,
                    args);

            slotTypeInfo.OnUnplugging(this, args);
        }

        internal void OnUnplugged(object sender, PlugEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.ExtensionUnplugged,
                    new object[]
                        {
                            args.plugInfo.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.plugInfo.extensionInfo.Name,
                            extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            extensionInfo.Name,
                            slotTypeInfo.Name
                        }));

            args.plugInfo.OnUnplugged(sender, args);

            if (Unplugged != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "Unplugged",
                        Name),
                    Unplugged,
                    this,
                    args);

            slotTypeInfo.OnUnplugged(this, args);
        }

        private static string FormatPlugInfos(IEnumerable<PlugInfo> plugs)
        {
            var builder = new StringBuilder();
            builder.Append('{');
            var d = 0;

            foreach (var plugInfo in plugs)
                builder.AppendFormat("{0}{1}:\"{2}\"", ++d == 1 ? "" : ",", plugInfo.Id, plugInfo.Name);

            builder.Append('}');
            return builder.ToString();
        }

        internal void OnSelectionChanged(SelectionChangedEventArgs args)
        {
            Runtime.OnLogEvent(
                this,
                new LogEventArgs(
                    MessageType.Info,
                    VerbosityLevel.Detailed,
                    Messages.SelectionChanged,
                    new object[]
                        {
                            args.SlotInfo.extensionInfo.id.ToString(CultureInfo.InvariantCulture),
                            args.SlotInfo.extensionInfo.Name,
                            FormatPlugInfos(args.AddedPlugs),
                            FormatPlugInfos(args.RemovedPlugs)
                        }));

            if (SelectionChanged != null)
                Runtime.InvokeEventHandler(
                    string.Format(
                        Messages.SlotEventHandlerMethodDesc,
                        "SelectionChanged",
                        Name),
                    SelectionChanged,
                    this,
                    args);

            slotTypeInfo.OnSelectionChanged(this, args);
        }

        internal delegate bool SelectPlugHandler(PlugInfo[] plugs);
    }
}

